#include "lex.yy.c"

// Variaveis de controle
int lookahead;
int debug = 0;

/**
*	Funcoes auxiliares
*/

// Iterador de tokens
void proximo() {
	lookahead = yylex();
	if (lookahead == ESPACO){
		proximo();
	}
}

// Procura por erros
void compara(int x) {
	if (lookahead == x) {
		proximo();
	}
	else{
		printf("Erro na linha %i, col %i: %s\n", lc, ncol, yytext);
	}
}

//--------------------------------------------//

/**
* Cabecalhos de Funcoes
*/
void programa();
void declaracoes();
void declara_var();
void declara_tipo();
void declara_const();
void tipo();
void arranjo();
void intervalo();
void enumerado();
void range();
void registro();
void subprogramas();
void function();
void procedure();
void bloco();
void instrucoes();
void controle();
void controle_if();
void controle_for();
void controle_while();
void controle_repeat();
void expressoes();
void expressoes_2();
void expressoes_3();
void termo();
void termo_linha();
void fator();
void acesso();
void acesso_linha();


/**
*	Gramatica
*/

// Programa (simbolo de partida)
void programa() {
	compara(PROGRAM);
	compara(IDENTIFICADOR);
	compara(PONTO_E_VIRGULA);
	declaracoes();
	subprogramas();
	bloco();
	compara(PONTO);
}

// Declaracoes
void declaracoes() {
	if (lookahead == VAR){
		compara(VAR);
		declara_var();
		declaracoes();
	}
	if (lookahead == TYPE){
		compara(TYPE);
		declara_tipo();
		declaracoes();
	}
	if (lookahead == CONST){
		compara(CONST);
		declara_const();
		declaracoes();
	}
}

// Constantes
void declara_const() {
	compara(IDENTIFICADOR);
	compara(OPERADOR_DE_ATRIBUICAO);
	compara(VALOR);
	compara(PONTO_E_VIRGULA);
	if (lookahead == IDENTIFICADOR)
		declara_const();
}

// Variaveis
void declara_var() {
	compara(IDENTIFICADOR);
	while (lookahead == VIRGULA) {
		compara(VIRGULA);
		compara(IDENTIFICADOR);
	}
	compara(DOIS_PONTOS);
	tipo();
	compara(PONTO_E_VIRGULA);
	if (lookahead == IDENTIFICADOR)
		declara_var();
}

// Tipos
void declara_tipo() {
	compara(IDENTIFICADOR);
	compara(DOIS_PONTOS);
	tipo();
	compara(PONTO_E_VIRGULA);
	if (lookahead == IDENTIFICADOR)
		declara_tipo();
}

void tipo() {
	switch (lookahead) {
		case INTEGER:
		compara(INTEGER);
		break;
		case BOOLEAN:
		compara(BOOLEAN);
		break;
		case REAL:
		compara(REAL);
		break;
		case CHAR:
		compara(CHAR);
		break;
		case ARRAY:
		arranjo();
		break;
		case ABRE_PARENTESIS:
		enumerado();
		break;
		case RECORD:
		registro();
		break;
		default:
		break;
	}
}

void arranjo() {
	compara(ARRAY);
	intervalo();
	compara(OF);
	tipo();
}

void intervalo() {
	compara(ABRE_COLCHETE);
	range();
	while(lookahead == VIRGULA) {
		compara(VIRGULA);
		range();
	}
	compara(FECHA_COLCHETE);
}

void enumerado() {
	compara(ABRE_PARENTESIS);
	range();
	compara(FECHA_PARENTESIS);
}

// Funcao auxiliar para (ACESSO|VALOR)..(ACESSO|VALOR)
void range() {
	if (lookahead == VALOR)
		compara(VALOR);
	else
		acesso();
	compara(OPERADOR_DE_SUBFAIXA);
	if (lookahead == VALOR)
		compara(VALOR);
	else
		acesso();
}

void registro() {
	compara(RECORD);
	compara(IDENTIFICADOR);
	while (lookahead == VIRGULA){
		compara(VIRGULA);
		compara(IDENTIFICADOR);
	}
	compara(DOIS_PONTOS);
	tipo();
	compara(END);
}

// Subprogramas
void subprogramas() {
	if (lookahead == FUNCTION){
		function();
		subprogramas();
	}
	if (lookahead == PROCEDURE) {
		procedure();
		subprogramas();
	}
}

void function() {
	compara(FUNCTION);
	compara(IDENTIFICADOR);
	compara(ABRE_PARENTESIS);
	while(lookahead != FECHA_PARENTESIS){
		proximo();
	}
	compara(FECHA_PARENTESIS);
	compara(DOIS_PONTOS);
	tipo();
	compara(PONTO_E_VIRGULA);
	declaracoes();
	bloco();
	compara(PONTO_E_VIRGULA);
}

void procedure() {
	compara(PROCEDURE);
	compara(IDENTIFICADOR);
	compara(ABRE_PARENTESIS);
	while(lookahead != FECHA_PARENTESIS){
		proximo();
	}
	compara(FECHA_PARENTESIS);
	compara(PONTO_E_VIRGULA);
	declaracoes();
	bloco();
	compara(PONTO_E_VIRGULA);
}

// Bloco BEGIN ... END
void bloco() {
	compara(BEGIN_);
	while(lookahead != END) {
		instrucoes();
		compara(PONTO_E_VIRGULA);
	}
	compara(END);
}

void instrucoes() {
	switch (lookahead){
		case BEGIN_:
		bloco();
		break;
		case IF:
		case FOR:
		case WHILE:
		case REPEAT:
		controle();
		break;
		case BREAK:
		compara(BREAK);
		break;
		default:
		expressoes();
		break;
	}
}

void controle() {
	switch (lookahead){
		case IF:
		controle_if();
		break;
		case FOR:
		controle_for();
		break;
		default:
		controle_while();
		break;
	}
}

void controle_if() {
	compara(IF);
	expressoes();
	compara(THEN);
	instrucoes();
	if (lookahead == ELSE) {
		compara(ELSE);
		instrucoes();
	}
}

void controle_for() {
	compara(FOR);
	expressoes();
	if (lookahead == TO)
		compara(TO);
	else
		compara(DOWNTO);
	expressoes();
	compara(STEP);
	expressoes();
	compara(DO);
	bloco();
}

void controle_while() {
	compara(WHILE);
	expressoes();
	compara(DO);
	instrucoes();
}

void expressoes() {
	termo();
	expressoes_3();
	expressoes_2();
}

// Condicionao ternario: ENTernaria ? ETernaria : ETernaria
void expressoes_2() {
	if (lookahead == CONDICIONAL_TERNARIO){
		compara(CONDICIONAL_TERNARIO);
		expressoes();
		compara(DOIS_PONTOS);
		expressoes();
		expressoes_3();
	}
}

void expressoes_3() {
	if (lookahead == SOMA || 
		lookahead == SUBTRACAO ||
		lookahead == MENOR ||
		lookahead == MENOR_OU_IGUAL ||
		lookahead == MAIOR	||
		lookahead == MAIOR_OU_IGUAL ||
		lookahead == IGUALDADE ||
		lookahead == DESIGUALDADE ||
		lookahead == OR) {
		compara(lookahead);
	termo();
	expressoes_2();
} else {
	return;
}
}

void termo() {
	fator();
	termo_linha();
}

void termo_linha() {
	if (lookahead == ASTERISCO ||
		lookahead == DIVISAO ||
		lookahead == AND) {
		compara(lookahead);
	fator();
	termo_linha();
}
}

void fator() {
	switch (lookahead) {
		case NOT:
		case SOMA:
		case SUBTRACAO:
		compara(lookahead);
		expressoes();
		break;
		case ABRE_PARENTESIS:
		compara(ABRE_PARENTESIS);
		expressoes();
		compara(FECHA_PARENTESIS);
		break;
		case VALOR:
		compara(VALOR);
		break;
		default:
		acesso();
		break;
	}
}

void acesso() {
	if (lookahead == ARROBA) {
		compara(ARROBA);
		acesso();
	} else {
		compara(IDENTIFICADOR);
		acesso_linha();
	}
}

void acesso_linha() {
	if (lookahead == ABRE_COLCHETE) {
		compara(ABRE_COLCHETE);
		expressoes();
		compara(FECHA_COLCHETE);
		acesso_linha();
	}
	if (lookahead == PONTO) {
		compara(PONTO);
		compara(IDENTIFICADOR);
		acesso_linha();
	}
	if (lookahead == OPERADOR_DE_ATRIBUICAO) {
		compara(OPERADOR_DE_ATRIBUICAO);
		expressoes();
	}
}

//--------------------------------------//

int main(int argc, char const *argv[])
{
	yyin = fopen(argv[1], "r");
	lookahead = yylex();
	programa();
	return 0;
}